package cn.edu.cuit.mockfns.fam;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import sun.misc.Signal;

import cn.edu.cuit.mockfns.BusinessLogic;
import cn.edu.cuit.mockfns.fam.processors.JobPool;
import cn.edu.cuit.mockfns.fam.transition.Transition;
import cn.edu.cuit.mockfns.fam.transition.TransitionMatcher;
import cn.edu.cuit.mockfns.fam.transition.TransitionTable;
import cn.edu.cuit.mockfns.fam.transition.exceptions.MultipleMatchersException;
import cn.edu.cuit.mockfns.fam.transition.exceptions.NoneCurrentStateException;
import cn.edu.cuit.mockfns.fam.transition.exceptions.NoneNextStateException;
import cn.edu.cuit.mockfns.fam.tuples.CommonEndState;
import cn.edu.cuit.mockfns.fam.tuples.CommonErrorState;
import cn.edu.cuit.mockfns.fam.tuples.CommonNoneState;
import cn.edu.cuit.mockfns.fam.tuples.CommonNullEvent;
import cn.edu.cuit.mockfns.fam.tuples.CommonStartState;
import cn.edu.cuit.mockfns.fam.tuples.Event;
import cn.edu.cuit.mockfns.fam.tuples.State;

public class FAMEngine
{
    private State previousState;
    private State currentState;
    private State nextState;
    private boolean isDepleted;
    private boolean isSuspended;
    private int jobId;
    private BusinessLogic businessLogic;

    //One currentState and one event match several matchers,
    //haven't implemented.
    private boolean isMultipleMatcher;

    //default value is 1, it's related to multiple matchers 
    //implementation which hasn't implemented. 
    private int maxMatcherCountForOneAssembly;

    private Event<?> currentEvent;

    //should this field uses ConcurrentLinkedQueue? 
    //should the threads handle this object concurrently?
    private ConcurrentLinkedQueue<Event<?>> eventQueue;

    private LinkedList<Event<?>> usedEventQueue;
    private TransitionTable transitionTable;

    //Transition history will be added in following order
    // [previousState, currentState, transition, currentEvent, rumtimeException, afterRunNanotime, beforeRunNanotime]
    private LinkedList<Object[]> transitionHistory;

    public FAMEngine()
    {
        eventQueue = new ConcurrentLinkedQueue<Event<?>>();
        usedEventQueue = new LinkedList<Event<?>>();
        currentState = new CommonStartState();
        nextState = new CommonEndState();
        isSuspended = false;
        isDepleted = false;
        transitionHistory = new LinkedList<Object[]>();

    }

    public FAMEngine(TransitionTable table)
    {
        this();
        transitionTable = table;
    }

    public void start( int jobId )
    {
        this.jobId = jobId;
    }

    public void sendCharacter()
    {

    }

    public boolean isDepleted()
    {
        return eventQueue.isEmpty();
    }

    /**
     * The FAM engine instance will suspends the current business logic tread until calls the
     * <code>resume()</code> method.
     * */
    public void suspend()
    {
        isSuspended = true;
    }

    /**
     * The FAM engine instance will resume the current business logic tread until calls the
     * <code>suspend()</code> method.
     * */
    public void resume()
    {
        isSuspended = false;
    }

    public boolean isSuspended()
    {
        return isSuspended;
    }

    public State getCurrentState()
    {
        return currentState;
    }

    public LinkedList<Object[]> getTransitionHistorry()
    {
        return transitionHistory;
    }

    /**
     * If there is no willing that you don't want stop
     * */
    public void rollBack()
    {

    }

    public Event<?> getLastedEvent()
    {
        Event<?> event = eventQueue.peek();
        return event;
    }

    public void addEvent( Event<?> event )
    {
        if( event == null )
        {
            event = new CommonNullEvent();
        }
        eventQueue.add( event );
        isDepleted = false;
    }

    /**
     * @see BusinessLogic annotations about thread model, now I haven't used singleton model.
     * */
    public static FAMEngine getNewInstance( TransitionTable table )
    {
        return new FAMEngine( table );
    }

    public void sendEvent( Event<?> event, FAMEngine famEngine )
    {
        currentEvent = event;

        addEvent( event );

        try
        {
            synchronized( this )
            {
                if( isSuspended )
                {
                    return;
                }

                while( true )
                {
                    if( !isDepleted )
                    {
                        Event<?> firstEventInQueue = eventQueue.peek();
                        currentEvent = firstEventInQueue;

                        List<TransitionMatcher> matchers = transitionTable.getTransitionMatcherForState( famEngine
                            .getCurrentState() );

                        Transition transition = null;
                        for( TransitionMatcher matcher : matchers )
                        {
                            int matcherCounter = 0;

                            if( ( transition = matcher.match( famEngine.getCurrentState(), firstEventInQueue ) ) != null )
                            {
                                if( ++matcherCounter > 1 )
                                {
                                    throw new MultipleMatchersException( "There are " + matcherCounter + " matchers" );
                                }
                            }
                        }

                        if( transition == null )
                        {
                            return;
                        }

                        if( transition.getFunction().accept( famEngine.getCurrentState(), firstEventInQueue ) )
                        {
                            doTransition( transition, famEngine );
                            eventQueue.remove();
                            usedEventQueue.add( firstEventInQueue );
                        }
                    }
                    if( eventQueue.isEmpty() )
                    {
                        isDepleted = true;
                        return;
                    }
                }
            }
        }
        catch( NoneCurrentStateException e )
        {
            transformToErrorState();
        }
        catch( NoneNextStateException e )
        {
            transformToErrorState();
        }
        catch( MultipleMatchersException e )
        {
            transformToErrorState();
        }
        catch( Exception e )
        {
            transformToErrorState();
        }
    }

    private void doTransition( Transition transition, FAMEngine famEngine )
    {
        RuntimeException rumtimException;
        if( ( currentState = famEngine.getCurrentState() ) == null )
        {
            //This exception message should be more explicit.
            rumtimException = new NoneCurrentStateException( "//TODO" );
            transitionHistory.add( new Object[] { previousState, currentState, transition, currentEvent,
                    rumtimException, null, null } );
            throw rumtimException;
        }

        previousState = currentState;

        if( ( currentState = transition.getNextState() ) == null )
        {
            //This exception message should be more explicit.
            rumtimException = new NoneNextStateException( "//TODO" );
            transitionHistory.add( new Object[] { previousState, currentState, transition, currentEvent,
                    rumtimException, null, null } );
            throw rumtimException;
        }

        //To-self transition
        if( ( currentState = transition.getNextState() ) instanceof CommonNoneState )
        {
            currentState = transition.getCurrentState();
        }

        long beforeRunNanotime = System.nanoTime();

        currentState.run();

        long afterRunNanotime = System.nanoTime();

        transitionHistory.add( new Object[] { previousState, currentState, transition, currentEvent, null,
                afterRunNanotime, beforeRunNanotime } );
    }

    private void transformToErrorState()
    {
        currentState = new CommonErrorState( this );
        currentState.run();
    }

    public void stop()
    {
        //Where to submit the transition history?
        //TODO
        submitTransitionHistory();

        JobPool.forceCloseJob( jobId );
    }

    private void submitTransitionHistory()
    {
        // TODO Auto-generated method stub

    }

    public void setCurrentState( State state )
    {
        currentState = state;
    }

    public void cleanEvents()
    {
        eventQueue = new ConcurrentLinkedQueue<Event<?>>();
    }

    public void raiseSignal( State state )
    {
        businessLogic.respondSignal( state );
    }

    public void installBusinessLogic( BusinessLogic businessLogic )
    {
        this.businessLogic = businessLogic;
    }
}
